#include <hls_stream.h>
#include <string.h>
#include <stdio.h>
#include "graph_fpga.h"


#include "fpga_global_mem_apply.h"

void applyFunction(
    int                             loopNum,
#if HAVE_APPLY_OUTDEG
    hls::stream<burst_raw>          &outDegreeStream,
#endif
    hls::stream<burst_raw>          &vertexPropStream,
    hls::stream<burst_raw>          &tmpVertexPropStream,
    unsigned int                    argReg,
    hls::stream<burst_raw>          &newVertexPropStream,
    int                             *outReg
)
{
    unsigned int infoArray[BURST_ALL_BITS / NODE_WIDTH];
#pragma HLS ARRAY_PARTITION variable=infoArray dim=0 complete
    for (int i = 0; i < BURST_ALL_BITS / NODE_WIDTH; i++)
    {
        infoArray[i] = 0;
    }
    for (int loopCount = 0; loopCount < loopNum; loopCount ++)
    {

#pragma HLS PIPELINE II=1
        burst_raw vertexProp;
        burst_raw tmpVertexProp;

        read_from_stream(vertexPropStream, vertexProp);
        read_from_stream(tmpVertexPropStream, tmpVertexProp);

#if HAVE_APPLY_OUTDEG
        burst_raw outDeg;
        read_from_stream(outDegreeStream, outDeg);
#endif

        burst_raw newVertexProp;

        for (int i = 0; i < BURST_ALL_BITS / NODE_WIDTH; i++)
        {
#pragma HLS UNROLL
            prop_t tProp     = tmpVertexProp.range((i + 1) * NODE_WIDTH - 1, i * NODE_WIDTH );
            prop_t uProp     = vertexProp.range(   (i + 1) * NODE_WIDTH - 1, i * NODE_WIDTH );
#if HAVE_APPLY_OUTDEG
            prop_t out_deg   = outDeg.range(       (i + 1) * NODE_WIDTH - 1, i * NODE_WIDTH );
#else
            prop_t out_deg   = 0;
#endif
            unsigned int tmpInfoArray[BURST_ALL_BITS / NODE_WIDTH];
#pragma HLS ARRAY_PARTITION variable=tmpInfoArray dim=0 complete
//#pragma HLS DEPENDENCE variable=tmpInfoArray inter false

            prop_t  wProp    = applyCalculation( tProp, uProp, out_deg, tmpInfoArray[i],  argReg);
            infoArray[i] += tmpInfoArray[i];

            newVertexProp.range((i + 1) * NODE_WIDTH - 1, i * NODE_WIDTH ) = wProp;

        }
        write_to_stream(newVertexPropStream, newVertexProp);
    }

    int infoAggregate = 0;

    for (int i = 0; i < BURST_ALL_BITS / NODE_WIDTH; i ++)
    {
        DEBUG_PRINTF("infoArray %d %d \n", i, infoArray[i]);
        infoAggregate += infoArray[i];
    }
    outReg[0] = infoAggregate;

}



void bitMapRead(
    uint_raw                        addrOffset,
    uint_raw                        vertexNum,
    uint16                          *input,
    hls::stream<uint16>                  &outputstream
){
#pragma HLS function_instantiate variable=input

	unsigned int offset = addrOffset/(512);
unsigned int num = ((vertexNum - 1) / ((512))) + 1;

	{

	timeLineLoop : for (unsigned int i = (offset); i < (offset + num); i ++)
	    {
			#pragma HLS PIPELINE II=1
			uint16 tmp = input[i];
			write_to_stream(outputstream, tmp);
	    }
	}
}

void bitMapRead_apply(
    uint_raw                        addrOffset,
    uint_raw                        vertexNum,
	hls::stream<uint16>                          &input1,
	hls::stream<uint16>                          &input2,
	hls::stream<uint16>                          &input3,
	hls::stream<uint16>                          &input4,
    hls::stream<bitmap_block512>                  &outputstream
){
	unsigned int offset = addrOffset/(512);
unsigned int num = ((vertexNum - 1) / ((512))) + 1;

	{

	timeLineLoop : for (unsigned int i = (offset); i < (offset + num); i ++)
	    {
			#pragma HLS PIPELINE II=1
			uint16 tmp1,tmp2,tmp3,tmp4;
			read_from_stream(input1, tmp1);
			read_from_stream(input2, tmp2);
			read_from_stream(input3, tmp3);
			read_from_stream(input4, tmp4);
			bitmap_block512 tmp;
			for(int j=0;j<512;j++){
				#pragma HLS UNROLL
				tmp.data.range(j, j)=tmp1.range(j, j)|tmp2.range(j, j)|tmp3.range(j, j)|tmp4.range(j, j);
			}
			tmp.flag=0;
			tmp.offset=i*512;
			if(tmp.data!=0)
				write_to_stream(outputstream, tmp);
	    }
	}
	{
		bitmap_block512 tmp;
		tmp.offset=0;
		tmp.data=0;
		tmp.flag=1;
		write_to_stream(outputstream, tmp);
	}
}

void bitMapFilter512_64_apply(hls::stream<bitmap_block512>         &bitMapStream512,
		hls::stream<bitmap_block64>         &bitMapStream64){
	while(true){
#pragma HLS PIPELINE II=8
		bitmap_block512 in;
		read_from_stream(bitMapStream512, in);
		if(in.flag==1){
			break;
		}
		for(int i=0;i<8;i++){
#pragma HLS UNROLL
			bitmap_block64 out;
			out.data=in.data.range(i*64+63,i*64);
			out.offset=in.offset+i*64;
			out.flag=0;
			if(out.data!=0){
				write_to_stream(bitMapStream64, out);
			}
		}
	}
	{
		bitmap_block64 out;
		out.data=0;
		out.offset=0;
		out.flag=1;
		write_to_stream(bitMapStream64, out);
	}
}
typedef struct {
	uint						  offset;
	ap_uint<16>    			      data;
    ap_uint<1>                    flag;
} bitmap_block16_apply;
void bitMapFilter64_16_apply(hls::stream<bitmap_block64>         &bitMapStream64,
		hls::stream<bitmap_block16_apply>         &bitMapStream16){
	while(true){
#pragma HLS PIPELINE II=4
		bitmap_block64 in;
		read_from_stream(bitMapStream64, in);
		if(in.flag==1){
			break;
		}
		for(int i=0;i<4;i++){
#pragma HLS UNROLL
			bitmap_block16_apply out;
			out.data=in.data.range(i*16+15,i*16);
			out.offset=in.offset+i*16;
			out.flag=0;
			if(out.data!=0){
				write_to_stream(bitMapStream16, out);
			}
		}
	}
	{
		bitmap_block16_apply out;
		out.data=0;
		out.offset=0;
		out.flag=1;
		write_to_stream(bitMapStream16, out);
	}
}

typedef struct {
	ap_uint<512>    			  data;
	uint addr;
    ap_uint<1>                    flag;
} vertex_tuples;

void vertexRead_apply(uint updatelevel,
	uint16	*vertex,
	hls::stream<bitmap_block16_apply>             &input,
    hls::stream<vertex_tuples>             &outputstream
)
{
#pragma HLS function_instantiate variable=input
#pragma HLS function_instantiate variable=outputstream

    {
    	while(true)
        {
			#pragma HLS PIPELINE II=1
    		bitmap_block16_apply in;
    		read_from_stream(input, in);
			if(in.flag==1){
				break;
			}
			uint16 tmp = vertex[in.offset/(512/NODE_WIDTH)];
			vertex_tuples tuples;
			for(int i=0;i<16;i++){
#pragma HLS UNROLL
				ap_uint<NODE_WIDTH> update;
				if(in.data.range(i, i)==1) update=updatelevel;
				else update=0;

				tuples.data.range(NODE_WIDTH*i+NODE_WIDTH-1,NODE_WIDTH*i)=PROP_COMPUTE_STAGE4(
								tmp.range(NODE_WIDTH*i+NODE_WIDTH-1,NODE_WIDTH*i),
								update);
			}
			tuples.addr=in.offset/(512/NODE_WIDTH);
			tuples.flag=0;
            write_to_stream(outputstream, tuples);
        }
    }
    {
    	vertex_tuples out;
    	out.data=0;
    	out.addr=0;
    	out.flag=1;
    	write_to_stream(outputstream, out);
    }
}

void vertexWrite_apply(
	uint16	*vertex,
	hls::stream<vertex_tuples>                          &updateStream
)
{
    {
    	while(true)
        {
			#pragma HLS PIPELINE II=1
    		vertex_tuples update;
    		read_from_stream(updateStream, update);
			if(update.flag==1){
				break;
			}
			vertex[update.addr]=update.data;
        }
    }
}

extern "C" {

void  vertexApply(
    unsigned int  vertexNum,
    unsigned int  addrOffset,
	uint updatelevel,
    unsigned int  argReg
){
#pragma HLS INTERFACE s_axilite port=vertexNum      bundle=control
#pragma HLS INTERFACE s_axilite port=argReg         bundle=control
#pragma HLS INTERFACE s_axilite port=addrOffset     bundle=control
#pragma HLS INTERFACE s_axilite port=updatelevel     bundle=control
#pragma HLS INTERFACE s_axilite port=return         bundle=control

	int counter=0;
	for(int i=addrOffset;i<vertexNum;i++){
		counter++;
	}
	counter+=updatelevel;
	counter+=argReg;
	return;
}

/*void  vertexApply(
    uint16        *vertexProp,
    uint16        *tmpVertexProp1,
    uint16        *tmpVertexProp2,
    uint16        *tmpVertexProp3,
    uint16        *tmpVertexProp4,
    uint16        *newVertexProp,
    unsigned int  vertexNum,
    unsigned int  addrOffset,
	uint updatelevel,
    unsigned int  argReg
)
{
#pragma HLS INTERFACE m_axi port=tmpVertexProp1 offset=slave bundle=gmem1   max_read_burst_length=16
#pragma HLS INTERFACE s_axilite port=tmpVertexProp1 bundle=control

#pragma HLS INTERFACE m_axi port=tmpVertexProp2 offset=slave bundle=gmem2   max_read_burst_length=16
#pragma HLS INTERFACE s_axilite port=tmpVertexProp2 bundle=control

#pragma HLS INTERFACE m_axi port=tmpVertexProp3 offset=slave bundle=gmem3   max_read_burst_length=16
#pragma HLS INTERFACE s_axilite port=tmpVertexProp3 bundle=control

#pragma HLS INTERFACE m_axi port=tmpVertexProp4 offset=slave bundle=gmem4   max_read_burst_length=16
#pragma HLS INTERFACE s_axilite port=tmpVertexProp4 bundle=control


#pragma HLS INTERFACE m_axi port=vertexProp offset=slave bundle=gmem6   max_read_burst_length=16
#pragma HLS INTERFACE s_axilite port=vertexProp bundle=control

#pragma HLS INTERFACE m_axi port=newVertexProp offset=slave bundle=gmem7   max_read_burst_length=16
#pragma HLS INTERFACE s_axilite port=newVertexProp bundle=control


#pragma HLS INTERFACE s_axilite port=vertexNum      bundle=control
#pragma HLS INTERFACE s_axilite port=argReg         bundle=control
#pragma HLS INTERFACE s_axilite port=addrOffset     bundle=control
#pragma HLS INTERFACE s_axilite port=updatelevel     bundle=control
#pragma HLS INTERFACE s_axilite port=return         bundle=control

#pragma HLS DATAFLOW

	hls::stream<uint16>	bitMapStream[4];
#pragma HLS stream variable=bitMapStream depth=512
#pragma HLS RESOURCE variable=bitMapStream core=FIFO_BRAM

    hls::stream<bitmap_block512>	bitMapStream512;
#pragma HLS stream variable=bitMapStream512 depth=512
#pragma HLS RESOURCE variable=bitMapStream512 core=FIFO_BRAM

    hls::stream<bitmap_block64>		bitMapStream64;
#pragma HLS stream variable=bitMapStream64 depth=512
#pragma HLS RESOURCE variable=bitMapStream64 core=FIFO_BRAM

    hls::stream<bitmap_block16_apply>		bitMapStream16;
#pragma HLS stream variable=bitMapStream16 depth=512
#pragma HLS RESOURCE variable=bitMapStream16 core=FIFO_BRAM

    hls::stream<vertex_tuples>		updateStream;
#pragma HLS stream variable=updateStream depth=512
#pragma HLS RESOURCE variable=updateStream core=FIFO_BRAM


    bitMapRead(addrOffset,vertexNum,tmpVertexProp1,bitMapStream[0]);
    bitMapRead(addrOffset,vertexNum,tmpVertexProp2,bitMapStream[1]);
    bitMapRead(addrOffset,vertexNum,tmpVertexProp3,bitMapStream[2]);
    bitMapRead(addrOffset,vertexNum,tmpVertexProp4,bitMapStream[3]);
    bitMapRead_apply(addrOffset, vertexNum,
    		bitMapStream[0],bitMapStream[1],bitMapStream[2],bitMapStream[3],
			bitMapStream512);
    bitMapFilter512_64_apply(bitMapStream512,bitMapStream64);
    bitMapFilter64_16_apply(bitMapStream64,bitMapStream16);
    vertexRead_apply(updatelevel,vertexProp,bitMapStream16,updateStream);
    vertexWrite_apply(newVertexProp,updateStream);
}
*/


/*
    void  vertexApply_beifen(
        uint16        *vertexProp,
        uint16        *tmpVertexProp1,
        uint16        *tmpVertexProp2,
        uint16        *tmpVertexProp3,
        uint16        *tmpVertexProp4,
		//uint16        *tmpVertexProp5,
		//uint16        *tmpVertexProp6,
		//uint16        *tmpVertexProp7,
		//uint16        *tmpVertexProp8,
        uint16        *newVertexProp1,
        uint16        *newVertexProp2,
        uint16        *newVertexProp3,
        uint16        *newVertexProp4,
		//uint16        *newVertexProp5,
		//uint16        *newVertexProp6,
		//uint16        *newVertexProp7,
		//uint16        *newVertexProp8,
#if HAVE_APPLY_OUTDEG
        uint16        *outDegree,
#endif
        int           *outReg,
        unsigned int  vertexNum,
        unsigned int  addrOffset,
        unsigned int  argReg
    )
    {




#pragma HLS INTERFACE m_axi port=tmpVertexProp1 offset=slave bundle=gmem1 max_read_burst_length=64 num_write_outstanding=4
#pragma HLS INTERFACE s_axilite port=tmpVertexProp1 bundle=control

#pragma HLS INTERFACE m_axi port=newVertexProp1 offset=slave bundle=gmem1_1 max_read_burst_length=64 num_write_outstanding=4
#pragma HLS INTERFACE s_axilite port=newVertexProp1 bundle=control



#pragma HLS INTERFACE m_axi port=tmpVertexProp2 offset=slave bundle=gmem2 max_read_burst_length=64 num_write_outstanding=4
#pragma HLS INTERFACE s_axilite port=tmpVertexProp2 bundle=control

#pragma HLS INTERFACE m_axi port=newVertexProp2 offset=slave bundle=gmem2_2 max_read_burst_length=64 num_write_outstanding=4
#pragma HLS INTERFACE s_axilite port=newVertexProp2 bundle=control



#pragma HLS INTERFACE m_axi port=tmpVertexProp3 offset=slave bundle=gmem3 max_read_burst_length=64 num_write_outstanding=4
#pragma HLS INTERFACE s_axilite port=tmpVertexProp3 bundle=control

#pragma HLS INTERFACE m_axi port=newVertexProp3 offset=slave bundle=gmem3_3 max_read_burst_length=64 num_write_outstanding=4
#pragma HLS INTERFACE s_axilite port=newVertexProp3 bundle=control



#pragma HLS INTERFACE m_axi port=tmpVertexProp4 offset=slave bundle=gmem4 max_read_burst_length=64 num_write_outstanding=4
#pragma HLS INTERFACE s_axilite port=tmpVertexProp4 bundle=control

#pragma HLS INTERFACE m_axi port=newVertexProp4 offset=slave bundle=gmem4_4 max_read_burst_length=64 num_write_outstanding=4
#pragma HLS INTERFACE s_axilite port=newVertexProp4 bundle=control



//#pragma HLS INTERFACE m_axi port=tmpVertexProp5 offset=slave bundle=gmem11 max_read_burst_length=64 num_write_outstanding=4
//#pragma HLS INTERFACE s_axilite port=tmpVertexProp5 bundle=control

//#pragma HLS INTERFACE m_axi port=newVertexProp5 offset=slave bundle=gmem11_11 max_read_burst_length=64 num_write_outstanding=4
//#pragma HLS INTERFACE s_axilite port=newVertexProp5 bundle=control



//#pragma HLS INTERFACE m_axi port=tmpVertexProp6 offset=slave bundle=gmem22 max_read_burst_length=64 num_write_outstanding=4
//#pragma HLS INTERFACE s_axilite port=tmpVertexProp6 bundle=control

//#pragma HLS INTERFACE m_axi port=newVertexProp6 offset=slave bundle=gmem22_22 max_read_burst_length=64 num_write_outstanding=4
//#pragma HLS INTERFACE s_axilite port=newVertexProp6 bundle=control



//#pragma HLS INTERFACE m_axi port=tmpVertexProp7 offset=slave bundle=gmem33 max_read_burst_length=64 num_write_outstanding=4
//#pragma HLS INTERFACE s_axilite port=tmpVertexProp7 bundle=control

//#pragma HLS INTERFACE m_axi port=newVertexProp7 offset=slave bundle=gmem33_33 max_read_burst_length=64 num_write_outstanding=4
//#pragma HLS INTERFACE s_axilite port=newVertexProp7 bundle=control



//#pragma HLS INTERFACE m_axi port=tmpVertexProp8 offset=slave bundle=gmem44 max_read_burst_length=64 num_write_outstanding=4
//#pragma HLS INTERFACE s_axilite port=tmpVertexProp8 bundle=control

//#pragma HLS INTERFACE m_axi port=newVertexProp8 offset=slave bundle=gmem44_44 max_read_burst_length=64 num_write_outstanding=4
//#pragma HLS INTERFACE s_axilite port=newVertexProp8 bundle=control



#pragma HLS INTERFACE m_axi port=outReg offset=slave bundle=gmem5
#pragma HLS INTERFACE s_axilite port=outReg bundle=control


#pragma HLS INTERFACE m_axi port=vertexProp offset=slave bundle=gmem6 max_read_burst_length=64
#pragma HLS INTERFACE s_axilite port=vertexProp bundle=control

#if HAVE_APPLY_OUTDEG

#pragma HLS INTERFACE m_axi port=outDegree offset=slave bundle=gmem7 max_read_burst_length=64
#pragma HLS INTERFACE s_axilite port=outDegree bundle=control

        hls::stream<burst_raw>      outDegreeStream;
#pragma HLS stream variable=outDegreeStream depth=256
        burstReadLite(addrOffset, vertexNum, outDegree, outDegreeStream);

#endif

#pragma HLS INTERFACE s_axilite port=vertexNum      bundle=control
#pragma HLS INTERFACE s_axilite port=argReg         bundle=control
#pragma HLS INTERFACE s_axilite port=addrOffset     bundle=control
#pragma HLS INTERFACE s_axilite port=return         bundle=control

#pragma HLS DATAFLOW

        hls::stream<burst_raw>      vertexPropStream;
#pragma HLS stream variable=vertexPropStream depth=2
#pragma HLS RESOURCE variable=vertexPropStream core=FIFO_LUTRAM

        hls::stream<burst_raw>      tmpVertexPropArray[CU_SIZE];
#pragma HLS stream variable=tmpVertexPropArray depth=2
#pragma HLS RESOURCE variable=tmpVertexPropArray core=FIFO_LUTRAM


        hls::stream<burst_raw>      tmpVertexPropStream;
#pragma HLS stream variable=tmpVertexPropStream depth=2
#pragma HLS RESOURCE variable=tmpVertexPropStream core=FIFO_LUTRAM



        hls::stream<burst_raw>      newVertexPropStream;
#pragma HLS stream variable=newVertexPropStream depth=2
#pragma HLS RESOURCE variable=newVertexPropStream core=FIFO_LUTRAM

        hls::stream<burst_raw>      newVertexPropArray[CU_SIZE];
#pragma HLS stream variable=newVertexPropArray depth=2
#pragma HLS RESOURCE variable=newVertexPropArray core=FIFO_LUTRAM

        int loopNum = (vertexNum/(512/NODE_WIDTH)) ;



        burstReadLite(addrOffset, vertexNum, vertexProp, vertexPropStream);


        burstReadLite(0, vertexNum, tmpVertexProp1, tmpVertexPropArray[0]);
        burstReadLite(0, vertexNum, tmpVertexProp2, tmpVertexPropArray[1]);
        burstReadLite(0, vertexNum, tmpVertexProp3, tmpVertexPropArray[2]);
        burstReadLite(0, vertexNum, tmpVertexProp4, tmpVertexPropArray[3]);
        //burstReadLite(0, vertexNum, tmpVertexProp5, tmpVertexPropArray[4]);
        //burstReadLite(0, vertexNum, tmpVertexProp6, tmpVertexPropArray[5]);
        //burstReadLite(0, vertexNum, tmpVertexProp7, tmpVertexPropArray[6]);
        //burstReadLite(0, vertexNum, tmpVertexProp8, tmpVertexPropArray[7]);

        cuMerge(loopNum, tmpVertexPropArray, tmpVertexPropStream);

        applyFunction(
            loopNum,
#if HAVE_APPLY_OUTDEG
            outDegreeStream,
#endif
            vertexPropStream,
            tmpVertexPropStream,
            argReg,
            newVertexPropStream,
            outReg
        );

        cuDuplicate(loopNum , newVertexPropStream,
                    newVertexPropArray);

        writeBackLite(vertexNum, newVertexProp1 + (addrOffset /(512/NODE_WIDTH)), newVertexPropArray[0]);
        writeBackLite(vertexNum, newVertexProp2 + (addrOffset /(512/NODE_WIDTH)), newVertexPropArray[1]);
        writeBackLite(vertexNum, newVertexProp3 + (addrOffset /(512/NODE_WIDTH)), newVertexPropArray[2]);
        writeBackLite(vertexNum, newVertexProp4 + (addrOffset /(512/NODE_WIDTH)), newVertexPropArray[3]);
        //writeBackLite(vertexNum, newVertexProp5 + (addrOffset /(512/NODE_WIDTH)), newVertexPropArray[4]);
        //writeBackLite(vertexNum, newVertexProp6 + (addrOffset /(512/NODE_WIDTH)), newVertexPropArray[5]);
        //writeBackLite(vertexNum, newVertexProp7 + (addrOffset >> 4), newVertexPropArray[6]);
        //writeBackLite(vertexNum, newVertexProp8 + (addrOffset >> 4), newVertexPropArray[7]);

    }
*/
}


